#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <sstream>
#include <iostream>
#include <string.h>

#include "logger.h"
#include "type_converter.h"

using namespace std;

#define MAX_FILE_PATH 4096
#define BUFFSIZE 256
#define LOG_PREFIX "LOGPREFIX"
#define ERROR_LOG_PREFIX "error"
#define PROGRAM_LOG_PREFIX "program"
#define BUSINESS_LOG_PREFIX "business"
#define LOG_LEVEL "LOGLEVEL"
#define LEVEL "level"
#define LOG_INTERVAL "LOGINTERVAL"
#define INTERVAL "interval"
#define LOG_KEY "LOGKEY"
#define KEY "key"

#define PERMS 0777

CLogger::CLogger() {
    m_logBuf = new char[IOBUFFSIZE];
    m_dataBuf = new char[IOBUFFSIZE];
    
    m_logInterval = LOG_INTERVAL_HOUR;
    m_key = "";
}

int CLogger::Init(const char* errorLogPrefix, const char* programLogPrefix, const char* businessLogPrefix,
                  int logLevel, int logInterval, const char* key) {
    int logLevel_shmid = -1;
    time_t tt = time(NULL);
    struct tm currentTime;
    int ret = -1;

    if (logLevel < LOGDEBUG || logLevel > LOGERROR ||
        logInterval < LOG_INTERVAL_HOUR || logInterval > LOG_INTERVAL_YEAR) {
        goto cleanup;
    }

    m_errorLogPrefix = errorLogPrefix;
    m_programLogPrefix = programLogPrefix;
    m_businessLogPrefix = businessLogPrefix;
    m_logInterval = logInterval;
    m_key = key;

    // 创建logLevel的共享内存
    logLevel_shmid = shmget(IPC_PRIVATE, sizeof(int), PERMS|IPC_CREAT);
    if (logLevel_shmid == -1) {
        goto cleanup;
    }

    m_level_memblock = (char *)shmat(logLevel_shmid, (char *)0, 0);
    if (m_level_memblock == (char *)-1) {
        goto cleanup;
    }

    if ((ret = SetLogLevel(logLevel)) != 0) {
        goto cleanup;
    }

    localtime_r(&tt, &currentTime);

    m_errorFd = _OpenLog(m_errorLogPrefix, currentTime);
    if (m_errorFd <= 0) {
        ret = -1;
        goto cleanup;
    }
    m_programFd = _OpenLog(m_programLogPrefix, currentTime);
    if (m_programFd <= 0) {
        ret = -1;
        goto cleanup;
    }
    m_businessFd = _OpenLog(m_businessLogPrefix, currentTime);
    if (m_businessFd <= 0) {
        ret = -1;
        goto cleanup;
    }
    m_errorLogTime = currentTime;
    m_programLogTime = currentTime;
    m_businessLogTime = currentTime;

cleanup:
    if (logLevel_shmid != -1) {
        // 提前删除，避免共享内存泄漏。
        // 系统删除的机制是在全部使用到它的process退出之后才会真正删除。
        shmctl(logLevel_shmid, IPC_RMID, 0);
    }

    return ret;
}

CLogger::~CLogger() {
    delete[] m_logBuf;
    delete[] m_dataBuf;
    
    if (m_errorFd > 0) {
        close(m_errorFd);
    }
    if (m_programFd > 0) {
        close(m_programFd);
    }
    if (m_businessFd) {
        close(m_businessFd);
    }
}

int CLogger::SetLogLevel(int level) {    
    // 将logLevel设置到共享内存中
    if (m_level_memblock == (char *)-1) {
        return -1;
    }

    memcpy(m_level_memblock, &level, sizeof(int));
    return 0;
}

int CLogger::GetLogLevel(int& level) {
    // 从共享内存中读取logLevel
    if (m_level_memblock == (char *)-1) {
        return -1;
    }

    memcpy(&level, m_level_memblock, sizeof(int));

    return 0;
}

SSMAP CLogger::ParseHttpGetParam(const string& request) {
    SSMAP retMap;
    char* buf = new char[request.size()+1];
    strcpy(buf, request.c_str());

    char* p = buf;
    char* key = p;
    char* value = NULL;
    while(*p != 0) {
        if(*p == '=') {
            *p = 0;
            value = p+1;
        } 
        else if(*p == '&') {
            *p = 0;
            retMap[key] = value;
            key = p+1;
            value = NULL;
        }
        else {
            if((unsigned char)(*p) >= 0x80)
                ++p;
        }
        ++p;
    }
    retMap[key] = value;
    delete[] buf;

    return retMap;
}

int CLogger::ChangeLogLevel(const char* uri, 
                            const char* request) {
    SSMAP paramMap = ParseHttpGetParam(request);

    if (paramMap["level"].empty() || paramMap["level"] == "" ||
        paramMap["key"].empty() || paramMap["key"] == "") {
        return -1;
    }

    int level;
    if (CTypeConverter::StrToInt(paramMap["level"], level) != 0) {
        return -1;
    }
    
    string key = paramMap["key"];

//cout << "level = " << level << endl;
//cout << "key = " << key << endl;

    if (level < LOGDEBUG || 
        level > LOGERROR ||
        key != m_key)   
        return -1;

    SetLogLevel(level); 
    return 0;
}

int CLogger::_GetLogFd(int logType,
                       const struct tm& currentTime) { 
    string prefix;
    int* fd;
    struct tm* logTime;
    
    switch (logType) {
        case LOG_TYPE_PROGRAM:
            prefix = m_programLogPrefix;
            fd = &m_programFd;
            logTime = &m_programLogTime;
            break;
        case LOG_TYPE_BUSINESS:
            prefix = m_businessLogPrefix;
            fd = &m_businessFd;
            logTime = &m_businessLogTime;
            break;
        case LOG_TYPE_ERROR:
            prefix = m_errorLogPrefix;
            fd = &m_errorFd;
            logTime = &m_errorLogTime;
            break;
        default:
            return -1;
    }

    if (_NeedNewLog(currentTime, *logTime)) {
        close(*fd);
        *fd = _OpenLog(prefix, currentTime);
        *logTime = currentTime;
    }

    return *fd;
}

int CLogger::_OpenLog(const string& prefix, 
                      const struct tm& currentTime) {
    string logName = prefix;
    char buf[16];
    
    memset(buf, 0, 16);

    switch (m_logInterval) {
        case LOG_INTERVAL_DAY:
             sprintf(buf, "-%04d%02d%02d.log", currentTime.tm_year + 1900,
                 currentTime.tm_mon + 1, currentTime.tm_mday);
             break;
        case LOG_INTERVAL_YEAR:
             sprintf(buf, "-%04d.log", currentTime.tm_year + 1900);
             break;
        case LOG_INTERVAL_MONTH:
             sprintf(buf, "-%04d%02d.log", currentTime.tm_year + 1900,
                 currentTime.tm_mon + 1);
             break;
        default: // hour
             sprintf(buf, "-%04d%02d%02d%02d.log", currentTime.tm_year + 1900, 
                 currentTime.tm_mon + 1, currentTime.tm_mday, currentTime.tm_hour);
             break;
    }

    logName += buf;

    int ret = open(logName.c_str(), O_CREAT|O_APPEND|O_WRONLY, S_IRWXU|S_IROTH);
    if (ret < 0) {
        cerr << "open log failed: \"" << logName << "\"" << endl; 
    }

    return ret;
}

bool CLogger::_NeedNewLog(const struct tm& currentTime, 
                         const struct tm& logTime) {
    switch (m_logInterval) {
        case LOG_INTERVAL_HOUR:
             return (logTime.tm_year != currentTime.tm_year) ||
                    (logTime.tm_mon != currentTime.tm_mon) ||
                    (logTime.tm_mday != currentTime.tm_mday) ||
                    (logTime.tm_hour != currentTime.tm_hour);
        case LOG_INTERVAL_DAY:
             return (logTime.tm_year != currentTime.tm_year) ||
                    (logTime.tm_mon != currentTime.tm_mon) ||
                    (logTime.tm_mday != currentTime.tm_mday);
        case LOG_INTERVAL_MONTH:
             return (logTime.tm_year != currentTime.tm_year) ||
                    (logTime.tm_mon != currentTime.tm_mon);
        case LOG_INTERVAL_YEAR:
             return (logTime.tm_year != currentTime.tm_year);
    }

    return false;
}

int CLogger::_ProgramLog(int level,
                         const char* logData) {
    int logLevel = LOGDEBUG;
    if (GetLogLevel(logLevel) != 0) {
        return -1;
    }
    
    if(level < logLevel) 
        return 0;

    const char *lvstr = "unknown";
    int logType;
    switch(level){
        case LOGDEBUG: 
            lvstr = "DEBUG"; 
            logType = LOG_TYPE_PROGRAM;
            break;
        case LOGINFO: 
            lvstr = "INFO"; 
            logType = LOG_TYPE_PROGRAM;
            break;
        case LOGWARN: 
            lvstr = "WARN"; 
            logType = LOG_TYPE_PROGRAM;
            break;
        case LOGERROR: 
            lvstr = "ERROR"; 
            logType = LOG_TYPE_ERROR;
            break;
    }
   
    time_t tt = time(NULL);
    struct tm currentTime;
    localtime_r(&tt, &currentTime);

    int fd = _GetLogFd(logType, currentTime);

    char date[48];
    CTypeConverter::TimeToStr(currentTime, date);

    int pid = getpid();
    // 进程id|时间|级别|异常描述（程序行,发生了什么事情，要详细，相关的参数要打印出来）[...]
    int len = snprintf(m_logBuf, IOBUFFSIZE, "%d|%s|%s|%s\n", pid, date, lvstr, logData);
    if(len >= IOBUFFSIZE){
        m_logBuf[IOBUFFSIZE-1] = '\n';
        len = IOBUFFSIZE;
    }
    
    _WriteLog(fd, m_logBuf, len);

    // 如果是error log需要多在program log里面记录一条日志
    if (logType == LOG_TYPE_ERROR) {
        fd = _GetLogFd(LOG_TYPE_PROGRAM, currentTime);
        _WriteLog(fd, m_logBuf, len);
    }
    return 0;
}

int CLogger::_BusinessLog(const char* logData) {
    time_t tt = time(NULL);
    struct tm currentTime;
    localtime_r(&tt, &currentTime);

    int fd = _GetLogFd(LOG_TYPE_BUSINESS, currentTime);

    char date[48];
    CTypeConverter::TimeToStr(currentTime, date);

    int pid = getpid();
    // pid |datetime|span|http code|method name|ecode|…|parameter
    int len = snprintf(m_logBuf, IOBUFFSIZE, "%d|%s|%s\n", pid, date, logData);
    if(len >= IOBUFFSIZE){
        m_logBuf[IOBUFFSIZE-1] = '\n';
        len = IOBUFFSIZE;
    }

    _WriteLog(fd, m_logBuf, len);
    return 0;
}

int CLogger::ProgramLog(int level, const char* format, ...) {
    va_list ap;
    va_start(ap, format);
    vsnprintf(m_dataBuf, IOBUFFSIZE, format, ap);
    va_end(ap);

    return _ProgramLog(level, m_dataBuf);
}

int CLogger::BusinessLog(const char* format, ...) {
    va_list ap;
    va_start(ap, format);
    vsnprintf(m_dataBuf, IOBUFFSIZE, format, ap);
    va_end(ap);

    return _BusinessLog(m_dataBuf);
}

int CLogger::_WriteLog(int fd, const void *buf, size_t size) {
//    assert(fd >= 0 && buf && size >= 0);
    const char *ptr = (const char *)buf;

    do {
        int writeSize = write(fd, ptr, size);
        switch(writeSize){
            case -1: return -1;
            case 0: break;
            default:
                ptr += writeSize;
                size -= writeSize;
                break;
        }
    } while(size > 0);

    return 0;
}


//add 2014-04-26
void CLogger::Release() {
}
